import { any } from "../../../core/logic";
import { Compute } from "./Compute";
import { gtao } from "./shaders/gtao";
import { Pass } from "../Pass";
import { Engine } from "../../../Engine";
import { guid } from "../../../core/utils";
/**
 * @class
 * @memberof w.compute
 */
class GTAOComputer extends Compute {
  constructor(opts = {}) {
    super(opts);
    this.initBuffer();
    this.initPipeLine();
    this.maxDistance = any(opts.maxDistance, 50);
    this.maxPixel = any(opts.maxPixel, 50);
    this.darkFactor = any(opts.darkFactor, 1);
    this.rayMarchSegment = any(opts.rayMarchSegment, 6);
    this.multiBounce = any(opts.multiBounce, false);
    this.blendColor = any(opts.blendColor, true);
    this.randomCount = 0;
  }
  set maxDistance(v) {
    this._maxDistance = v;
    Engine.instance.queue.writeBuffer(this.configBuffer, 0, new Float32Array([v]));
    this.update();
    return this;
  }
  get maxDistance() {
    return this._maxDistance;
  }
  set maxPixel(v) {
    this._maxPixel = v;
    Engine.instance.queue.writeBuffer(this.configBuffer, 4, new Float32Array([v]));
    this.update();
    return this;
  }
  get maxPixel() {
    return this._maxPixel;
  }
  set darkFactor(v) {
    // v = clamp(v, 0.01, 1);
    this._darkFactor = v;
    Engine.instance.queue.writeBuffer(this.configBuffer, 8, new Float32Array([v]));
    this.update();
    return this;
  }
  get darkFactor() {
    return this._darkFactor;
  }
  set rayMarchSegment(v) {
    this._rayMarchSegment = v;
    Engine.instance.queue.writeBuffer(this.configBuffer, 12, new Float32Array([v]));
    this.update();
    return this;
  }
  get rayMarchSegment() {
    return this._rayMarchSegment;
  }
  set multiBounce(v) {
    this._multiBounce = v;
    Engine.instance.queue.writeBuffer(this.configBuffer, 16, new Float32Array([v ? 1 : 0]));
    this.update();
    return this;
  }
  get multiBounce() {
    return this._multiBounce;
  }
  set blendColor(v) {
    this._blendColor = v;
    Engine.instance.queue.writeBuffer(this.configBuffer, 20, new Float32Array([v ? 1 : 0]));
    this.update();
    return this;
  }
  get blendColor() {
    return this._blendColor;
  }
  initBuffer() {
    // this.cameraBuffer= Engine.instance.device.createBuffer({
    //   label: "cameraBuffer",
    //   size: 32 * 4,
    //   usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
    // });
    this.configBuffer = Engine.instance.device.createBuffer({
      label: "configBuffer",
      size: 8 * 4,
      usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
    });
    this.randomBuffer = Engine.instance.device.createBuffer({
      label: "randomBuffer",
      size: 8 * 2 * 4,
      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
    });
    let pixels = Engine.instance.size[0] * Engine.instance.size[1];
    this.aoBuffer = Engine.instance.device.createBuffer({
      label: "aoBuffer",
      size: pixels * 4,
      usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
    });

  }
  randomDirection() {
    this.randomCount++;
    if (this.randomCount > 1) this.randomCount = 0;
    let offsetAngle = (Math.PI * 2) / 16;
    let angleSegment = (Math.PI * 2) / 8;
    let directionsArray = new Float32Array(8 * 2);
    for (let i = 0; i < 8; i++) {
      let angle = offsetAngle + i * angleSegment;
      directionsArray[i * 2] = Math.sin(angle);
      directionsArray[i * 2 + 1] = Math.cos(angle);
    }

    return directionsArray;
  }
  initPipeLine() {
    this.computePipeline = Engine.instance.device.createComputePipeline({
      layout: `auto`,
      compute: {
        module: Engine.instance.device.createShaderModule({
          code: gtao,
        }),
        entryPoint: "main",
      },
    });

  }
  initBindGroup() {
    if (this?.gtaoTexture?.width !== Engine.instance.size[0] || this?.gtaoTexture?.height !== Engine.instance.size[1]) {
      if (this.gtaoTexture) {
        this.gtaoTexture.destroy()
        this.gtaoTexture=null
      }
      this.gtaoTexture = Engine.instance.device.createTexture({
        size: Engine.instance.size,
        usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING,
        format: 'rgba16float',
      });
      this.gtaoTexture.id=guid();
      this.gtaoTexture.view=this.gtaoTexture.createView({label:"gtaoTexture"}) 
    }
    const positionView = Pass.instance().postionTexture.view
    const normalView = Pass.instance().normalTexture.view
    const colorView = Pass.instance().readAttach.view;
    const cameraBuffer = Engine.instance.scene.getCamera().getParam("camera").gpu.buffer;
    if(this.bindGroup && this.bindGroup.id != this.gtaoTexture.id){
      this.bindGroup=null;
    }
    if(!this.bindGroup){
      this.bindGroup = Engine.instance.device.createBindGroup({
        layout: this.computePipeline.getBindGroupLayout(0),
        entries: [
          {
            binding: 0,
            resource: {
              buffer: cameraBuffer,
            },
          },
          {
            binding: 1,
            resource: {
              buffer: this.configBuffer,
            },
          },
          {
            binding: 2,
            resource: {
              buffer: this.randomBuffer,
            },
          },
          {
            binding: 3,
            resource: {
              buffer: this.aoBuffer,
            },
          },
          {
            binding: 4,
            resource: positionView,
          },
          {
            binding: 5,
            resource: normalView,
          },
          {
            binding: 6,
            resource: colorView,
          },
          {
            binding: 7,
            resource: this.gtaoTexture.view,
          },
        ],
      });
      this.bindGroup.id=this.gtaoTexture.id;
    }
   
  }
  onCompute(commandEncoder) {
    this.initBindGroup()
    const directionsArray = this.randomDirection();
    Engine.instance.queue.writeBuffer(this.randomBuffer, 0, directionsArray);
    const camera = Engine.instance.scene.getCamera()
    // Engine.instance.queue.writeBuffer(this.cameraBuffer, 0, dataCamera);
    let nearData = new Float32Array([camera.near])
    let farData = new Float32Array([camera.far])
    Engine.instance.queue.writeBuffer(this.configBuffer, 24, nearData);
    Engine.instance.queue.writeBuffer(this.configBuffer, 28, farData);
    const workerSizeX = Math.ceil(Engine.instance.size[0] / 8);
    const workerSizeY = Math.ceil(Engine.instance.size[1] / 8);
    const workerSizeZ = 1;
    const computePass = commandEncoder.beginComputePass();
    computePass.setPipeline(this.computePipeline);
    computePass.setBindGroup(0, this.bindGroup);
    computePass.dispatchWorkgroups(workerSizeX, workerSizeY, workerSizeZ);
    computePass.end();
    // const commands = computeCommandEncoder.finish({ label: "GTAO" });
    // Engine.instance.queue.submit([commands]);
  }
}
export { GTAOComputer };
